
package pt.inescid.components.appadaptationlayer;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Vector;

import pt.inescid.components.api.AppAdaptationLayerI;
import pt.inescid.components.api.Component;
import pt.inescid.components.appadaptationlayer.applications.povray.CombinePPM;
import pt.inescid.components.appadaptationlayer.applications.povray.HandleINI;
import pt.inescid.components.appadaptationlayer.applications.sumunifs.BuildGraph;
import pt.inescid.components.appadaptationlayer.applications.sumunifs.HandleDAT;
import pt.inescid.components.commons.Resources;
import pt.inescid.core.GridP2P;
import pt.inescid.utils.Log;
import pt.inescid.utils.ReadXML;



/**
 * Application Adaptation Layer. The Application Adaptation Layer consists in a customization over a generic
 * Job Manager. 
 * - This layer defines which applications are supported by the local machine. Therefore, specific
 * mechanisms for handling each of these applications are provided. For example, launching applications with
 * the correct parameters and input files. 
 * - Moreover, these mechanisms are built on loading time and are based
 * on formal application descriptions.
 * 
 * This class is hard-coded for POVray. In the future it should get 
 * information from XML configuration files.
 * 
 * @author Sergio Esteves (sesteves at gsd.inesc-id.pt)
 *
 */
public class AppAdaptationLayer extends Component implements AppAdaptationLayerI {	
	
	private String tempDir;
	
	private HashMap<String,String> applications = new HashMap<String,String>();	
	
	private BuildGraph buildGraph;
	
	private Hashtable<Integer,int[]> state = new Hashtable<Integer,int[]>();
	
	//creation of the class Application Adaptation Layer of the application.
	public AppAdaptationLayer(GridP2P gridP2P, String tempDir) {
		super(gridP2P);
		this.tempDir = tempDir;		
		buildGraph = new BuildGraph(tempDir);
	}
	//reading the application configuration from the xml file.
	//TODO really read from from xml file ( could use XML parser - jdom)
	private void loadAppsConfig() {
		ReadXML readXML = new ReadXML("applications.xml");
		String tag, appId, cmdName = null;
	
		for(int i = 0; ; i++) {
			if((tag = readXML.getTag("identifier", i)) != null) 
				appId = tag;
			else
				break;
			if((tag = readXML.getTag("cmdName", i)) != null) cmdName = tag;
			
			applications.put(appId, cmdName);//puts into hashtable the applicationID and command to execute
		}
		
	}
	//starting(initiation) the Application Adaptation Layer of the application.	
	public void init() {
		loadAppsConfig();	
		log.write(this, "AppAdaptationLayer initialized", Log.FINE);
	}
	
	public String launchApp(String appID, String configLocation, String dataLocation) {
		log.write(this, "launchApp called (appID: " + appID + ", configLocation: " + 
				configLocation + ", dataLocation: " + dataLocation + ")", Log.FINER);		
		String outputLocation = null, cmd = null;

		//configuration only for povray.
		//TODO adapt for other applications.
		if("POVray".equals(appID)) {
			outputLocation = dataLocation.replace(".pov", ".ppm");
			cmd = applications.get("POVray") + " " + configLocation + " " +
				dataLocation;
			try {
				Process app = Runtime.getRuntime().exec(cmd, null, new File(tempDir));
				if(app.waitFor() == 0)
					return outputLocation;
			} catch (IOException e) {
				log.write(this, "IOException occurred: " + e.getMessage(), Log.SEVERE);
			} catch (InterruptedException e) {
				log.write(this, "InterruptedException occurred: " + e.getMessage(), Log.SEVERE);
			}
		} 
		return null;
	}
	//configuration only for Sumunifs. Different input arguments.
	//TODO i need only one method for launching the application that will read
	//the configuration from the xml file.
	public String launchApp(String appID, byte[] config, String outputLocation) {
		log.write(this, "launchApp called (appID: " + appID + ", outputLocation: " + 
				outputLocation + ")", Log.FINER);
		if("Sumunifs".equals(appID)) {
			String cmd = applications.get("Sumunifs");
			try {
				Process app = Runtime.getRuntime().exec(cmd, null, new File(tempDir));
				OutputStream os = app.getOutputStream();
				os.write(config);
				os.close();
				app.waitFor();					
				new File(tempDir + "sumunifs_out").renameTo(new File(tempDir + outputLocation));
				return outputLocation;
			} catch (IOException e) {
				log.write(this, "IOException occurred: " + e.getMessage(), Log.SEVERE);
			} catch (InterruptedException e) {
				log.write(this, "InterruptedException occurred: " + e.getMessage(), Log.SEVERE);
			}
		}
		return null;
	}
	//no idea what it does...
	public Resources launchAppTrial(String appID, String configLocation, String dataLocation) {
		log.write(this, "launchAppTrial called (appID: " + appID + ", configLocation: " + 
				configLocation + ", dataLocation: " + dataLocation + ")", Log.FINER);
		
		if("POVray".equals(appID)) {			
			String resolution = HandleINI.getResolution(configLocation),
				iniLocation = "GridP2P_config_trial.ini",
				outputLocation = "GridP2P_data_trial.ppm";
						
			if(HandleINI.generateFile(tempDir + iniLocation, resolution, 30, 33, outputLocation) < 0)
				return null;

			String cmd = applications.get(appID) + " " + iniLocation + 
			" " + dataLocation.substring(dataLocation.lastIndexOf('/') + 1);

			try {
				//TODO check time between
				Process app = Runtime.getRuntime().exec(cmd, null, new File(tempDir));
				app.waitFor();
			} catch (IOException e) {
				log.write(this, "IOException occurred: " + e.getMessage(), Log.SEVERE);
				return null;
			} catch (InterruptedException e) {
				log.write(this, "InterruptedException occurred: " + e.getMessage(), Log.SEVERE);
				return null;
			}

			long storage = new File(dataLocation.replaceFirst(".pov", ".bmp")).length();
			return new Resources(0,0,0, storage) ;
		}
		return null;
	}
	
	//TODO change for working with more applications!
	public byte[] buildConfig(String appID, String configLocation, int n, int nTasks) {
		if("POVray".equals(appID)) {
			String res = HandleINI.getResolution(configLocation);						
			int height = Integer.parseInt(res.split("H")[1]);
			int range = height / nTasks;
			int endRow = (n+1) == nTasks ? height : (n+1)*range;
			return HandleINI.generate(res, n*range + 1, endRow);			
		}
		if("Sumunifs".equals(appID)) {
			String str = HandleDAT.getConfigStr(configLocation);
			str = str + " :" + n;
			String[] lines = str.split("\n");
			long ntrials = Long.parseLong(lines[3].substring(0, lines[3].indexOf(" ")));
			long taskTrials = ntrials / nTasks;
			if(n == 0)
				taskTrials += ntrials % nTasks;
			lines[3] = lines[3].replaceFirst(Long.toString(ntrials), Long.toString(taskTrials));
			String result = "";
			for(String l : lines)
				result = result + l + "\n";
			log.write(this, "config file for task " + n + ": " + result, Log.FINER);			
			return result.getBytes();
		}
		return null;
	}
	
	public void createState(String appID, int index, String configLocation) {
		if("Sumunifs".equals(appID)) {
			int klass = HandleDAT.getKlass(configLocation) + 1;
			state.put(index, new int[klass]);
		}
	}
	
	public void updateState(String appID, int index, byte[] data) {
		if("Sumunifs".equals(appID)) {
			if(!state.containsKey(index)) return;
			int[] v = state.get(index);

			String dataStr = new String(data);
			String[] strs = dataStr.split("\n");
			for(int i = 0; i < v.length; i++)
				v[i] += Integer.parseInt(strs[i].replaceAll("^\\s+", ""));
		}
	}
	
	public byte[] buildOutput(String appID, Vector<byte[]> slices) {
		log.write(this, "buildOutput called (appID: " + appID +
				", slices: " + slices + ")", Log.FINER);
		if("POVray".equals(appID)) {
			byte[] output = slices.get(0);
			for(int i = 1; i < slices.size(); i++)
				output = CombinePPM.combine(output, slices.get(i));
			return output;
		}
		return null;
	}
	
	public boolean buildOutput(String appID, int index, String configLocation, String outputLocation) {
		log.write(this, "buildOutput called (appID: " + appID + ", index: " + index + 
				", configLocation: " + configLocation + ", outputLocation: " + outputLocation + 
				")", Log.FINER);
		if("Sumunifs".equals(appID)) {
			if(!state.containsKey(index)) return false;
			
			double[] item = HandleDAT.getItems(configLocation);			
			int[] v = state.get(index);					
			buildGraph.makeGraph(v, item[2], item[0], item[1], outputLocation);
			state.remove(index);
			return true;
		}		
		return false;		
	}
	
	public String getDataLocation(String appID, List<String> input) {
		if("POVray".equals(appID)) {
			for(String location : input)
				if(location.endsWith(".pov"))
					return location;
		}
		return null;
	}

	public String getConfigLocation(String appID, List<String> input) {
		if("POVray".equals(appID)) {
			for(String location : input)
				if(location.endsWith(".ini"))
					return location;
		}
		if("Sumunifs".equals(appID)) {
			for(String location : input)
				if(location.endsWith(".dat"))
					return location;
		}
		return null;
	}
		
	public String[] getAppIDs() {		
		return applications.keySet().toArray(new String[]{});
	}
	
	public String getConfigExt(String appID) {
		if("POVray".equals(appID)) return ".ini";
		if("Sumunifs".equals(appID)) return ".txt";
		return null;
	}
	
	public String getDataExt(String appID) {
		if("POVray".equals(appID)) return ".pov";
		return null;
	}
	
	public boolean isStream(String appID) {
		if("POVray".equals(appID)) return false;
		if("Sumunifs".equals(appID)) return true;
		return false;
	}
}
